//
// Created by Tony on 2024/6/12.
//

#include "ConstantPool.h"
#include "ClassRef.h"
#include "FieldRef.h"
#include "MethodRef.h"
#include "SymRef.h"
#include <cmath>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <memory>
#include <ostream>
#include <sstream>

namespace RtCode {

using std::endl;
using std::make_shared;
using std::setw;
using std::stringstream;

uint16_t ConstantPool::addInt64(int64_t val) {
    for (uint16_t i = 0; i < mConstantPool.size(); i++) {
        auto constant = mConstantPool[i];
        if (constant.tag == ConstantTag::INT64 && constant.value.int64 == val) {
            return i;
        }
    }

    TaggedConstantValue constant{.tag = ConstantTag::INT64, .value = {.int64 = val}};

    mConstantPool.push_back(constant);
    return mConstantPool.size() - 1;
}

int64_t ConstantPool::getInt64(uint16_t index) const { return mConstantPool[index].value.int64; }

uint16_t ConstantPool::addFlt64(double val) {
    for (uint16_t i = 0; i < mConstantPool.size(); i++) {
        auto constant = mConstantPool[i];
        if (constant.tag == ConstantTag::FLT64 && fabs(mConstantPool[i].value.flt64 - val) < 1e-6) {
            return i;
        }
    }

    TaggedConstantValue constant{.tag = ConstantTag::FLT64, .value = {.flt64 = val}};

    mConstantPool.push_back(constant);
    return mConstantPool.size() - 1;
}

double ConstantPool::getFlt64(uint16_t index) const { return mConstantPool[index].value.flt64; }

uint16_t ConstantPool::addUtf8(const string& utf8) {
    for (uint16_t i = 0; i < mConstantPool.size(); i++) {
        auto& constant = mConstantPool[i];
        if (constant.tag == ConstantTag::UTF8 &&
            strcmp(utf8.c_str(), constant.utf8->c_str()) == 0) {
            return i;
        }
    }

    TaggedConstantValue constant{.tag = ConstantTag::UTF8};
    constant.utf8 = make_shared<string>(utf8);

    mConstantPool.push_back(constant);
    return mConstantPool.size() - 1;
}

const char* ConstantPool::getUtf8(uint16_t index) const {
    return mConstantPool[index].utf8->c_str();
}

uint16_t ConstantPool::addString(const string& str) {
    auto index = addUtf8(str);

    TaggedConstantValue constant{.tag = ConstantTag::STRING, .value{.string = index}};
    mConstantPool.push_back(constant);
    return mConstantPool.size() - 1;
}

const char* ConstantPool::getString(uint16_t index) const {
    auto utf8Index = mConstantPool[index].value.string;
    return getUtf8(utf8Index);
}

uint16_t ConstantPool::addClassRef(const string& className) {
    auto classNameIndex = addUtf8(className);

    for (uint16_t i = 0; i < mConstantPool.size(); i++) {
        if (mConstantPool[i].tag == ConstantTag::CLASS_REF &&
            mConstantPool[i].classRef->getClassNameIndex() == classNameIndex) {
            return i;
        }
    }

    auto ref = make_shared<ClassRef>(shared_from_this(), classNameIndex);
    TaggedConstantValue constant{.tag = ConstantTag::CLASS_REF, .classRef = ref};
    mConstantPool.push_back(constant);
    return mConstantPool.size() - 1;
}

ClassRef* ConstantPool::getClassRef(uint16_t index) const {
    return mConstantPool[index].classRef.get();
}

uint16_t ConstantPool::addFieldRef(const string& className, const string& fieldName) {
    auto classNameIndex = addUtf8(className);
    auto fieldNameIndex = addUtf8(fieldName);

    for (uint16_t i = 0; i < mConstantPool.size(); i++) {
        if (mConstantPool[i].tag == ConstantTag::FIELD_REF &&
            mConstantPool[i].fieldRef->getClassNameIndex() == classNameIndex &&
            mConstantPool[i].fieldRef->getMemberNameIndex() == fieldNameIndex) {
            return i;
        }
    }

    auto constPool = shared_from_this();
    auto ref = make_shared<FieldRef>(constPool, classNameIndex, fieldNameIndex);
    TaggedConstantValue constant{.tag = ConstantTag::FIELD_REF, .fieldRef = ref};
    mConstantPool.push_back(constant);
    return mConstantPool.size() - 1;
}

FieldRef* ConstantPool::getFieldRef(uint16_t index) const {
    return mConstantPool[index].fieldRef.get();
}

uint16_t ConstantPool::addMethodRef(const string& className, const string& methodName) {
    auto classNameIndex = addUtf8(className);
    auto methodNameIndex = addUtf8(methodName);

    for (uint16_t i = 0; i < mConstantPool.size(); i++) {
        if (mConstantPool[i].tag == ConstantTag::METHOD_REF &&
            mConstantPool[i].methodRef->getClassNameIndex() == classNameIndex &&
            mConstantPool[i].methodRef->getMemberNameIndex() == methodNameIndex) {
            return i;
        }
    }

    auto ref = make_shared<MethodRef>(shared_from_this(), classNameIndex, methodNameIndex);
    TaggedConstantValue constant{.tag = ConstantTag::METHOD_REF, .methodRef = ref};
    mConstantPool.push_back(constant);
    return mConstantPool.size() - 1;
}

MethodRef* ConstantPool::getMethodRef(uint16_t index) const {
    return mConstantPool[index].methodRef.get();
}

string ConstantPool::toString() const {
    stringstream ss;
    constexpr int W_NAME = 8;
    constexpr char SCOPE_SEP[] = "$";
    constexpr char COMMENT[] = "    // ";

    ss << "Constant pool:" << endl;
    for (int i = 0; i < mConstantPool.size(); i++) {
        ss << std::left << setw(2) << i << " :  ";
        const auto& constant = mConstantPool[i];
        switch (constant.tag) {
            case ConstantTag::INT64:
                ss << setw(W_NAME) << "int64"
                   << " : " << constant.value.int64;
                break;
            case ConstantTag::FLT64:
                ss << setw(W_NAME) << "flt64"
                   << " : " << constant.value.flt64;
                break;
            case ConstantTag::UTF8:
                ss << setw(W_NAME) << "utf8"
                   << " : " << constant.utf8->c_str();
                break;
            case ConstantTag::STRING:
                ss << setw(W_NAME) << "string"
                   << " : " << constant.value.string << COMMENT << getString(i);
                break;
            case ConstantTag::CLASS_REF:
                ss << setw(W_NAME) << "class"
                   << " : " << constant.classRef->getClassNameIndex() << COMMENT
                   << constant.classRef->className();
                break;
            case ConstantTag::FIELD_REF:
                ss << setw(W_NAME) << "field"
                   << " : " << constant.fieldRef->getClassNameIndex() << SCOPE_SEP
                   << constant.fieldRef->getMemberNameIndex() << COMMENT
                   << constant.fieldRef->className() << SCOPE_SEP
                   << constant.fieldRef->memberName();
                break;
            case ConstantTag::METHOD_REF:
                ss << setw(W_NAME) << "method"
                   << " : " << constant.methodRef->getClassNameIndex() << SCOPE_SEP
                   << constant.methodRef->getMemberNameIndex() << COMMENT
                   << constant.methodRef->className() << SCOPE_SEP
                   << constant.methodRef->memberName();
                break;
            default:
                break;
        }
        ss << endl;
    }
    return ss.str();
}

} // namespace RtCode